package com.tarry.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;

@Slf4j
public class SocketServer {
    private Selector selector;
    private final static int DEFAULT_PORT = 3333;
    private int port;
    public SocketServer() {
        this.port = DEFAULT_PORT;
    }

    public SocketServer(int port) {
        this.port = port;
    }

    public void start() {
        try {
            final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            // 设置Socket 为非阻塞
            serverSocketChannel.configureBlocking(false);
            // 获取该Channel 关联的服务端
            final ServerSocket socket = serverSocketChannel.socket();
            // 绑定服务端地址
            serverSocketChannel.bind(new InetSocketAddress(port));
            // 获取一个Selector
            this.selector = Selector.open();
            // 注册Channel到Selector 选择OP_ACCEPT
            /**
             * SelectionKey.OP_READ 可读事件
             * SelectionKey.OP_WRITE 可写事件
             * SelectionKey.OP_CONNECT 客户端连接时，触发
             * SelectionKey.OP_ACCEPT 服务端发生连接请求时，触发
             */
//            SelectionKey selectionKey1 = serverSocketChannel.register(
//                    this.selector,
//                    0,  // 不关注任何事件
//                    null);
//            // 关注连接事件
//            selectionKey1.interestOps(SelectionKey.OP_ACCEPT);

            serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);


            while (true) {
                // 减少CPU空转
                if (this.selector.select(1000) == 0) {// 此处阻塞,有事件发生或等待超时时，回复运行，当事件未进行处理时，不会进入阻塞状态
                    log.warn("服务端等待了1秒，无连接～～～");
                    continue;
                }

                // 需要处理的事件 包含所有的事件
                final Set<SelectionKey> selectionKeys = this.selector.selectedKeys();

                final Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    final SelectionKey selectionKey = iterator.next();
                    // 手动移除 避免重复操作出现空指针异常
                    iterator.remove();

                    // 处理事件
                    this.handleEvent(selectionKey);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void handleEvent(SelectionKey selectionKey) throws IOException {
        SocketChannel client;
        // 判断事件类型
        if (selectionKey.isAcceptable()) { // 接受连接
            final ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
            client = server.accept();
            if (Objects.isNull(client)) {
                return;
            }
            // 设置为非阻塞模式
            client.configureBlocking(false);
            // 设置为对OP_READ事件感兴趣
            client.register(this.selector, SelectionKey.OP_READ);

        }else if (selectionKey.isReadable()) { // 读事件
            client = (SocketChannel) selectionKey.channel();
            try {
                final ByteBuffer receiverBuffer = ByteBuffer.allocate(1024);
                receiverBuffer.clear();
                // 读取消息
                final int count = client.read(receiverBuffer);
                if ( count == -1) {
                    // 连接正常关闭 返回 -1
                    selectionKey.cancel();
                    // 关闭通道
                    client.close();

                }else if ( count > 0 ){
                    final String text = new String(receiverBuffer.array(), 0, count);
                    log.info("receive client msg: {}" , text);
                    // 响应消息
                    final ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
                    sendBuffer.clear();
                    client = (SocketChannel) selectionKey.channel();
                    String sendContent = new Date() + ":Hello client.";
                    sendBuffer.put(sendContent.getBytes(StandardCharsets.UTF_8));
                    sendBuffer.flip();
                    client.write(sendBuffer);
                    log.info("receive msg to client: {}", sendContent );
                }else {
                    log.warn("read length is zero !");
                }
            }catch (IOException ioException) {
                ioException.printStackTrace();
                // 取消当前事件监听
                selectionKey.cancel();
                // 关闭当前通道
                client.close();
            }
        }else {
            log.warn("未关注的其他事件~~~~~");
        }
    }

    public static void main(String[] args) {
        final SocketServer server = new SocketServer();
        server.start();
    }

}
